package br.com.hs.nfe.util;

import br.com.hs.nfe.common.app.AppConfig;
import br.com.hs.nfe.common.enumeration.ServicoSefazEnum;
import br.com.hs.nfe.common.exception.HSCommonException;
import br.com.hs.nfe.util.exception.HSUtilException;
import br.com.hs.nfe.common.util.*;
import java.io.*;
import java.util.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Classe de utilidades da NF-e.
 * @author Ranlive Hrysyk
 */
public class UtilHelper {

    private static UtilHelper utilHelper;
    private List<EstadoTO> listaEstado;
    private List<PaisTO> listaPais;
    private List<CfopTO> listaCFOPTransp;
    private List<CfopTO> listaCFOP;
    private List<NcmTO> listaNCM;
    private List<ServicoTO> listaServico;
    private HashMap<String, List<MunicipioTO>> mapMunicipio;
    private Map<EstadoTO, Map> mapMunUF = null;
    private Map<ServicoSefazEnum, String> mapServicoSefaz = null;
    private static Document document = null;
    private static final String TOKEN = "|";
    private static final String ARQUIVO_EXTENSAO = ".txt";
    private static final String ARQUIVO_ESTADO = "Estado";
    private static final String ARQUIVO_PAIS = "Pais";
    private static final String ARQUIVO_CFOP_TRANSPORTE = "CfopTransp";
    private static final String ARQUIVO_CFOP = "Cfop";
    private static final String ARQUIVO_NCM = "Ncm";
    private static final String ARQUIVO_SERVICO = "Servico";
    private static final String TAG_UF = "uf";
    private static final String TAG_SIGLA = "sigla";

    /**
     * Cria e retorna uma instância da classe UtilHelper.
     */
    public static UtilHelper getInstance() throws HSUtilException {
        if (utilHelper == null) {
            utilHelper = new UtilHelper();
        }
        return utilHelper;
    }

    /**
     * Construtor privado para ocultar a instânciação.
     */
    private UtilHelper() throws HSUtilException {
        createListaEstadoTO();
    }

    /**
     * Cria a lista de estados.
     */
    private void createListaEstadoTO() throws HSUtilException {
        listaEstado = new ArrayList<EstadoTO>();
        BufferedReader br = getBufferedReader(ARQUIVO_ESTADO);
        try {
            while (br.ready()) {
                EstadoTO estado = new EstadoTO();
                String linha = br.readLine();
                StringTokenizer stringTokenizer = new StringTokenizer(linha, TOKEN);
                estado.setCodigo(stringTokenizer.nextToken());
                estado.setSigla(stringTokenizer.nextToken());
                estado.setDescricao(stringTokenizer.nextToken());
                listaEstado.add(estado);
            }
            listaEstado = Collections.unmodifiableList(listaEstado);
        } catch (FileNotFoundException e) {
            throw new HSUtilException(e.getMessage(), e);
        } catch (NumberFormatException e) {
            throw new HSUtilException(e.getMessage(), e);
        } catch (IOException e) {
            throw new HSUtilException(e.getMessage(), e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                }
            }
        }
    }

    /**
     * Cria a lista de MunicipioTO por EstadoTO.
     */
    private void createListaMuncipioTOByEstadoTO(EstadoTO estado) throws HSUtilException {
        BufferedReader br = null;
        List listMunicipio = new ArrayList();
        MunicipioTO municipio = null;
        try {
            br = getBufferedReader(estado.getCodigo());
            while (br.ready()) {
                String linha = br.readLine();
                StringTokenizer stringTokenizer = new StringTokenizer(linha, TOKEN);
                municipio = new MunicipioTO();
                municipio.setCodigo(stringTokenizer.nextToken());
                municipio.setDescricao(stringTokenizer.nextToken());
                listMunicipio.add(municipio);
            }
            Collections.sort(listMunicipio);
            getMapMunicipio().put(estado.getCodigo(), listMunicipio);
        } catch (FileNotFoundException e) {
            throw new HSUtilException(e.getMessage(), e);
        } catch (NumberFormatException e) {
            throw new HSUtilException(e.getMessage(), e);
        } catch (IOException e) {
            throw new HSUtilException(e.getMessage(), e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                }
            }
        }
    }

    /**
     * Cria a lista de CfopTO de transporte.
     */
    private List<CfopTO> createListaCfopTOTransp() throws HSUtilException {
        BufferedReader br = null;
        if (listaCFOPTransp == null) {
            listaCFOPTransp = new ArrayList();
            try {
                br = getBufferedReader(ARQUIVO_CFOP_TRANSPORTE);
                while (br.ready()) {
                    listaCFOPTransp.add(new CfopTO(br.readLine()));
                }
            } catch (IOException e) {
                throw new HSUtilException(e.getMessage(), e);
            } finally {
                if (br != null) {
                    try {
                        br.close();
                    } catch (IOException e) {
                    }
                }
            }
        }
        return listaCFOPTransp;
    }

    /**
     * Cria a lista de CfopTO.
     */
    private List<CfopTO> createListaCfopTO() throws HSUtilException {
        BufferedReader br = null;
        if (listaCFOP == null) {
            listaCFOP = new ArrayList();
            try {
                br = getBufferedReader(ARQUIVO_CFOP);
                while (br.ready()) {
                    listaCFOP.add(new CfopTO(br.readLine()));
                }
            } catch (IOException e) {
                throw new HSUtilException(e.getMessage(), e);
            } finally {
                if (br != null) {
                    try {
                        br.close();
                    } catch (IOException e) {
                    }
                }
            }
        }
        return listaCFOP;
    }

    /**
     * Cria a lista de NcmTO.
     */
    private List<NcmTO> createListaNcmTO() throws HSUtilException {
        NcmTO ncm = null;
        BufferedReader br = null;
        if (listaNCM == null) {
            listaNCM = new ArrayList();
            try {
                br = getBufferedReader(ARQUIVO_NCM);
                while (br.ready()) {
                    ncm = new NcmTO();
                    String linha = br.readLine();
                    StringTokenizer stringTokenizer = new StringTokenizer(linha, TOKEN);
                    ncm.setCodigo(stringTokenizer.nextToken().trim());
                    ncm.setDescricao(stringTokenizer.nextToken());
                    listaNCM.add(ncm);
                }
            } catch (IOException e) {
                throw new HSUtilException(e.getMessage(), e);
            } finally {
                if (br != null) {
                    try {
                        br.close();
                    } catch (IOException e) {
                    }
                }
            }
        }
        return listaNCM;
    }

    /**
     * Cria a lista de ServicoTO.
     */
    private List<ServicoTO> createListaServicoTO() throws HSUtilException {
        BufferedReader br = null;
        ServicoTO servico = null;
        if (listaServico == null) {
            listaServico = new ArrayList();
            try {
                br = getBufferedReader(ARQUIVO_SERVICO);
                while (br.ready()) {
                    servico = new ServicoTO();
                    String linha = br.readLine();
                    StringTokenizer stringTokenizer = new StringTokenizer(linha, TOKEN);
                    servico.setCodigo(stringTokenizer.nextToken().trim());
                    servico.setNome(stringTokenizer.nextToken());
                    listaServico.add(servico);
                }
            } catch (IOException e) {
                throw new HSUtilException(e.getMessage(), e);
            } finally {
                if (br != null) {
                    try {
                        br.close();
                    } catch (IOException e) {
                    }
                }
            }
        }
        return listaServico;
    }

    /**
     * Obtém o arquivo TXT.
     */
    private BufferedReader getBufferedReader(String fileName) {
        return new BufferedReader(new InputStreamReader(UtilHelper.class.getResourceAsStream(fileName + ARQUIVO_EXTENSAO)));
    }

    /**
     * Obtém a lista de estados.
     */
    private List<EstadoTO> getListaEstado() throws HSUtilException {
        return listaEstado;
    }

    /**
     * Obtém um Map contente Código do estado como chave e uma lista como resultado.
     */
    private HashMap<String, List<MunicipioTO>> getMapMunicipio() {
        if (mapMunicipio == null) {
            mapMunicipio = new HashMap();
        }
        return mapMunicipio;
    }

    /**
     * Cria o mapa de serviços da sefaz.
     */
    private void createMapServicosSefaz(ServicoSefazEnum servicoSefazEnum) throws HSUtilException {
        mapMunUF = new HashMap();
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder db = dbf.newDocumentBuilder();
            document = db.parse(getArquivoURLUf());
            Element elem = document.getDocumentElement();
            NodeList nl = elem.getElementsByTagName(TAG_UF);

            for (int i = 0; i < nl.getLength(); i++) {
                mapServicoSefaz = new HashMap();
                Element tagUF = (Element) nl.item(i);
                String sigla = tagUF.getAttribute(TAG_SIGLA);

                for (ServicoSefazEnum enum1 : (ServicoSefazEnum[]) servicoSefazEnum.getClass().getEnumConstants()) {
                    mapServicoSefaz.put(enum1, getTagAttribute(tagUF, enum1.getNomeServico(), null));
                }
                mapMunUF.put(getEstadoTOBySigla(sigla), mapServicoSefaz);
            }
        } catch (ParserConfigurationException e) {
            throw new HSUtilException(e.getMessage(), e);
        } catch (SAXException e) {
            throw new HSUtilException(e.getMessage(), e);
        } catch (IOException e) {
            throw new HSUtilException(e.getMessage(), e);
        } catch (HSCommonException e) {
            throw new HSUtilException(e.getMessage(), e);
        }
    }

    /**
     * Obtém o valor da TAG do XML.
     */
    private static String getTagAttribute(Element elem, String tagName, String tagAttribute) {
        NodeList children = elem.getElementsByTagName(tagName);
        if (children == null) {
            return null;
        }
        Element child = (Element) children.item(0);
        if (child == null) {
            return null;
        }
        if (child.getFirstChild() != null) {
            return child.getFirstChild().getNodeValue();
        }
        return child.getAttribute(tagAttribute);
    }

    /**
     * Obtém o arquivo XML dos serviços da Sefaz.
     */
    private InputStream getArquivoURLUf() throws HSCommonException {
        return UtilHelper.class.getResourceAsStream(AppConfig.getInstance().getArquivoURLSefaz());
    }

    /**
     * Obtém um estado pelo código.
     * @param codigo Código do estado.
     */
    public EstadoTO getEstadoTOByCodigo(String codigo) throws HSUtilException {
        EstadoTO estado = new EstadoTO(codigo, "", "");
        if (getListaEstado().contains(estado)) {
            estado = (EstadoTO) getListaEstado().get(getListaEstado().indexOf(estado));
        } else if (codigo.equals(HSConstants.CODIGO_UF_EXTERIOR)) {
            estado.setSigla(HSConstants.SIGLA_UF_EXTERIOR);
            estado.setDescricao(HSConstants.NOME_UF_MUNICIPIO_EXTERIOR);
        } else {
            estado = null;
        }
        return estado;
    }

    /**
     * Obtém um estado pela UF.
     * @param sigla Sigla da UF.
     */
    public EstadoTO getEstadoTOBySigla(String sigla) throws HSUtilException {
        if (!StringHelper.isBlankOrNull(sigla)) {
            EstadoTO estado = new EstadoTO("", sigla, "");
            if (getListaEstado().contains(estado)) {
                estado = (EstadoTO) getListaEstado().get(getListaEstado().indexOf(estado));
            } else if (sigla.equals(HSConstants.SIGLA_UF_EXTERIOR)) {
                estado.setCodigo(HSConstants.CODIGO_UF_EXTERIOR);
                estado.setDescricao(HSConstants.NOME_UF_MUNICIPIO_EXTERIOR);
            } else if (sigla.equals(HSConstants.SIGLA_RFB)) {
                estado = new EstadoTO("", HSConstants.SIGLA_RFB, HSConstants.NOME_RFB);
            } else if (sigla.equals(HSConstants.SIGLA_SUFRAMA)) {
                estado = new EstadoTO("", HSConstants.SIGLA_SUFRAMA, HSConstants.NOME_SUFRAMA);
            } else {
                estado = null;
            }
            return estado;
        }
        return null;
    }

    /**
     * Obétem uma lista pelo país.
     * @param paisTO País.
     */
    public List<EstadoTO> getEstadoTOByPaisTO(PaisTO paisTO) throws HSUtilException {
        if (!StringHelper.isBlankOrNull(paisTO.getCodigo())) {
            if (!paisTO.getCodigo().equals(HSConstants.BRASIL)) {
                List list = new ArrayList();
                EstadoTO estado = null;
                estado = new EstadoTO(HSConstants.CODIGO_UF_EXTERIOR, HSConstants.SIGLA_UF_EXTERIOR, HSConstants.NOME_UF_MUNICIPIO_EXTERIOR);
                list.add(estado);
                return list;
            }
            return getListaEstado();
        }
        return null;
    }

    /**
     * Obtém uma lista com os estados.
     * @param incluirExterior Incluir estados do exterior.
     */
    public List<EstadoTO> listEstadoTO(boolean incluirExterior) throws HSUtilException {
        if (incluirExterior) {
            List list = new ArrayList();
            list.addAll(getListaEstado());
            EstadoTO exterior = new EstadoTO(HSConstants.CODIGO_UF_EXTERIOR, HSConstants.SIGLA_UF_EXTERIOR, HSConstants.NOME_UF_MUNICIPIO_EXTERIOR);
            list.add(exterior);
            return list;
        }
        return getListaEstado();
    }

    /**
     * Obtém uma lista de municípios por estado.
     * @param estado Estado.
     */
    public List<MunicipioTO> listMunicipioTOByEstadoTO(EstadoTO estado) throws HSUtilException {
        Map municipio = getMapMunicipio();
        if ((!municipio.containsKey(estado.getCodigo())) && (!estado.getCodigo().equals(HSConstants.CODIGO_UF_EXTERIOR))) {
            createListaMuncipioTOByEstadoTO(estado);
        } else if (estado.getCodigo().equals(HSConstants.CODIGO_UF_EXTERIOR)) {
            List listaExterior = new ArrayList();
            MunicipioTO municipioTO = new MunicipioTO();
            municipioTO.setCodigo(HSConstants.CODIGO_MUNICIPIO_EXTERIOR);
            municipioTO.setDescricao(HSConstants.NOME_UF_MUNICIPIO_EXTERIOR);
            listaExterior.add(municipioTO);
            municipio.put(estado.getCodigo(), listaExterior);
        }
        return new ArrayList((Collection) municipio.get(estado.getCodigo()));
    }

    /**
     * Obtém municípío pelo nome e estado.
     * @param estado Estado.
     * @param nomeMunicipio Nome do Município.
     */
    public MunicipioTO getMunicipioTOByEstadoTOAndName(EstadoTO estado, String nomeMunicipio) throws HSUtilException {
        if (!StringHelper.isBlankOrNull(nomeMunicipio)) {
            HashMap municipio = getMapMunicipio();
            if (!municipio.containsKey(estado.getCodigo())) {
                listMunicipioTOByEstadoTO(estado);
            }
            List listMunicipio = (List) municipio.get(estado.getCodigo());
            Iterator it = listMunicipio.iterator();
            while (it.hasNext()) {
                MunicipioTO municipioTO = (MunicipioTO) it.next();
                if (municipioTO.getDescricao().equalsIgnoreCase(nomeMunicipio.trim())) {
                    return municipioTO;
                }
            }
        }
        return null;
    }

    /**
     * Obtém município pelo código.
     * @param codigo Código do Município.
     */
    public MunicipioTO getMunicipioTOByCodigo(String codigo) throws HSUtilException {
        if ((!StringHelper.isBlankOrNull(codigo)) && (!codigo.equals("0"))) {
            String codigoEstado = codigo.substring(0, 2);
            EstadoTO estado = new EstadoTO(codigoEstado, "", "");
            HashMap municipio = getMapMunicipio();
            if (!municipio.containsKey(estado.getCodigo())) {
                listMunicipioTOByEstadoTO(estado);
            }
            List listMunicipio = (List) municipio.get(estado.getCodigo());
            MunicipioTO municipioTO = new MunicipioTO();
            municipioTO.setCodigo(codigo);
            int index = listMunicipio.indexOf(municipioTO);
            if (index != -1) {
                return (MunicipioTO) listMunicipio.get(index);
            }
        }
        return null;
    }

    /**
     * Obtém lista de países.
     */
    public List<PaisTO> listPaisTO() throws HSUtilException {
        BufferedReader br = null;
        try {
            if (listaPais == null) {
                br = getBufferedReader(ARQUIVO_PAIS);
                PaisTO paisTO = null;
                listaPais = new ArrayList();
                while (br.ready()) {
                    String linha = br.readLine();
                    StringTokenizer stringTokenizer = new StringTokenizer(linha, TOKEN);
                    paisTO = new PaisTO();
                    paisTO.setCodigo(stringTokenizer.nextToken().trim());
                    paisTO.setDescricao(stringTokenizer.nextToken().trim());
                    listaPais.add(paisTO);
                }
            }
        } catch (FileNotFoundException e) {
            throw new HSUtilException(e.getMessage(), e);
        } catch (IOException e) {
            throw new HSUtilException(e.getMessage(), e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                }
            }
        }
        return new ArrayList(listaPais);
    }

    /**
     * Obtém país pelo código.
     * @param codigo Código do país
     */
    public PaisTO getPaisTOByCodigo(String codigo) throws HSUtilException {
        PaisTO pais = new PaisTO();
        pais.setCodigo(codigo);
        if (listaPais == null) {
            listPaisTO();
        }
        if (listaPais.contains(pais)) {
            pais = (PaisTO) listaPais.get(listaPais.indexOf(pais));
        } else {
            pais.setCodigo("");
            pais.setDescricao("");
        }
        return pais;
    }

    /**
     * Obtém país pelo nome.
     * @param nomePais Nome do País
     */
    public PaisTO getPaisTOByName(String nomePais) throws HSUtilException {
        List l = listPaisTO();
        nomePais = nomePais.trim();
        Iterator it = l.iterator();
        while (it.hasNext()) {
            PaisTO paisObj = (PaisTO) it.next();
            if (paisObj.getDescricao().equalsIgnoreCase(nomePais)) {
                return paisObj;
            }
        }
        return null;
    }

    /**
     * Valida o código do pais.
     * @param cPais Código do pais
     */
    public boolean validarCodigoPais(String cPais) throws HSUtilException {
        PaisTO pais = getPaisTOByCodigo(cPais);
        return (pais != null) && (!StringHelper.isBlankOrNull(pais.getDescricao()));
    }

    /**
     * Valida o código da UF.
     * @param cUF Código da UF
     */
    public boolean validarCodigoUF(String cUF) throws HSUtilException {
        EstadoTO estado = getEstadoTOByCodigo(cUF);
        return (estado != null) && (!StringHelper.isBlankOrNull(estado.getDescricao()));
    }

    /**
     * Valida a sigla da UF.
     * @param uf Sigla da UF
     */
    public boolean validarUF(String uf) throws HSUtilException {
        EstadoTO estado = getEstadoTOBySigla(uf);
        return (estado != null) && (!StringHelper.isBlankOrNull(estado.getDescricao()));
    }

    /**
     * Valida o código do município.
     * @param cMun Código do Município
     */
    public boolean validarCodigoMunicipio(String cMun) throws HSUtilException {
        MunicipioTO municipio = getMunicipioTOByCodigo(cMun);
        return (municipio != null) && (!StringHelper.isBlankOrNull(municipio.getDescricao()));
    }

    /**
     * Valida se o código do município pertence a UF.
     * @param cMun Código do Município
     * @param uf Sigla da UF
     */
    public boolean validarMunicipioByUF(String cMun, String uf) {
        try {
            if ((!StringHelper.isBlankOrNull(uf)) && (!StringHelper.isBlankOrNull(cMun))) {
                EstadoTO estadoTO = getEstadoTOBySigla(uf);
                if (estadoTO != null) {
                    for (MunicipioTO municipioTO : listMunicipioTOByEstadoTO(estadoTO)) {
                        if (municipioTO.getCodigo().equals(cMun)) {
                            return true;
                        }
                    }
                }
            }
        } catch (HSUtilException e) {
            return false;
        }
        return false;
    }

    /**
     * Valida o código do CFOP.
     * @param cfop Código CFOP
     */
    public boolean validarCFOP(String cfop) throws HSUtilException {
        CfopTO cfopTO = getCfopTOByCodigo(cfop);
        return (cfopTO != null);
    }

    /**
     * Obtém uma lista de CfopTO de trasnporte.
     */
    public List<CfopTO> listCfopTOTransporte() throws HSUtilException {
        if (listaCFOPTransp == null) {
            createListaCfopTOTransp();
        }
        return listaCFOPTransp;
    }

    /**
     * Obtém CFOP de transporte pelo código.
     * @param codigo Código CFOP
     */
    public CfopTO getCfopTOTransporteByCodigo(String codigo) throws HSUtilException {
        if (listaCFOPTransp == null) {
            createListaCfopTOTransp();
        }
        if (!StringHelper.isBlankOrNull(codigo)) {
            CfopTO cfop = new CfopTO(codigo);
            int index = listaCFOPTransp.indexOf(cfop);
            if (index >= 0) {
                return (CfopTO) listaCFOPTransp.get(index);
            }
        }
        return null;
    }

    /**
     * Obtém um CFOP pelo código.
     * @param codigo Código CFOP.
     */
    public CfopTO getCfopTOByCodigo(String codigo) throws HSUtilException {
        if (listaCFOP == null) {
            createListaCfopTO();
        }
        if (!StringHelper.isBlankOrNull(codigo)) {
            CfopTO cfop = new CfopTO(codigo);
            int index = listaCFOP.indexOf(cfop);
            if (index >= 0) {
                return (CfopTO) listaCFOP.get(index);
            }
        }
        return null;
    }

    /**
     * Obtém uma lista de CfopTO.
     */
    public List<CfopTO> listCfopTO() throws HSUtilException {
        if (listaCFOP == null) {
            createListaCfopTO();
        }
        return listaCFOP;
    }

    /**
     * Obtém um NCM pelo código.
     * @param codigo Código NCM.
     */
    public NcmTO getNcmTOByCodigo(String codigo) throws HSUtilException {
        if (listaNCM == null) {
            createListaNcmTO();
        }
        if (!StringHelper.isBlankOrNull(codigo)) {
            NcmTO ncm = new NcmTO();
            ncm.setCodigo(codigo);
            int index = listaNCM.indexOf(ncm);
            if (index >= 0) {
                return (NcmTO) listaNCM.get(index);
            }
        }
        return null;
    }

    /**
     * Obtém uma lista NcmTO.
     */
    public List<NcmTO> listNcmTO() throws HSUtilException {
        if (listaNCM == null) {
            createListaNcmTO();
        }
        return listaNCM;
    }

    /**
     * Obtém um Servico pelo código.
     * @param codigo Código serviço.
     */
    public ServicoTO getServicoTOByCodigo(String codigo) throws HSUtilException {
        if (listaServico == null) {
            createListaServicoTO();
        }
        if (!StringHelper.isBlankOrNull(codigo)) {
            ServicoTO servico = new ServicoTO();
            servico.setCodigo(codigo);
            return (ServicoTO) listaServico.get(listaServico.indexOf(servico));
        }
        return null;
    }

    /**
     * Obtém uma lista de ServivoTO.
     */
    public List<ServicoTO> listServicoTO() throws HSUtilException {
        if (listaServico == null) {
            createListaServicoTO();
        }
        return listaServico;
    }

    /**
     * Obtém a URL do serviço da Sefaz para determinado estado.
     */
    public String getURLByUF(String uf, ServicoSefazEnum sefazEnum) throws HSUtilException {
        if (mapMunUF == null) {
            createMapServicosSefaz(sefazEnum);
        }
        return (String) ((Map) mapMunUF.get(getEstadoTOBySigla(uf))).get(sefazEnum);
    }
}
